#pragma once

#include "Common.hpp"
#include "ObjectPool.hpp"
#include "PageMap.hpp"

// 设计成单例模式
class PageCache
{
public:
    static PageCache *GetInstance()
    {
        return &Pg_Cache;
    }

    // PageCache提供的CentralCache获取一个k页的span的函数
    Span *NewSpan(size_t k)
    {
        assert(k > 0);

        // 如果申请的内存大小超过128页的大小，就直接去堆上申请空间
        if (k > NPAGES - 1)
        {
            void *ptr = SystemAlloc(k);
            //  Span* span = new Span;
            Span *span = SpanPool_.New();
            span->pageId_ = (PAGE_ID)ptr >> PAGE_SHIFT;
            span->n_ = k;

            // IdToSpan_[span->pageId_] = span;
            IdToSpan_.set(span->pageId_, span);

            return span;
        }

        // 先检查第 k 个桶里面有没有Span，如果有，直接返回
        if (!spanlists_[k].Empty())
        {
            Span *kSpan = spanlists_[k].PopFront();

            // *将页号与对应Span的映射存入hash表 IdToSpan_
            // 通过内存对象的地址可以算出它属于哪个页，进而通过IdToSpan找到其所属Span
            // 这样在回收的时候就可以将多个内存对象合并出完整的Span
            for (PAGE_ID i = 0; i < kSpan->n_; i++)
            {
                // IdToSpan_[kSpan->pageId_ + i] = kSpan;
                IdToSpan_.set(kSpan->pageId_ + i, kSpan);
            }

            return kSpan;
        }

        // 检查后面的桶有没有Span，如果有就进行切分
        for (size_t i = k + 1; i < NPAGES; i++)
        {
            if (!spanlists_[i].Empty())
            {
                /* 将大块为 n 页的Span切分成 k 页span和 n-k 页的span。
                k页的span返回，n-k页的span挂回桶里面 n-k的位置 */
                Span *nSpan = spanlists_[i].PopFront();
                // Span *kSpan = new Span;
                Span *kSpan = SpanPool_.New();

                kSpan->pageId_ = nSpan->pageId_;
                kSpan->n_ = k;

                nSpan->pageId_ += k;
                nSpan->n_ -= k;
                spanlists_[nSpan->n_].PushFront(nSpan);

                // 将Span的首页号与尾页号存入hash表 IdToSpan_
                // 方便Span去合并其它Span的时候，可以快速找到能够与它合并的Span
                // IdToSpan_[nSpan->pageId_] = nSpan;
                // IdToSpan_[nSpan->pageId_ + nSpan->n_ - 1] = nSpan;
                IdToSpan_.set(nSpan->pageId_, nSpan);
                IdToSpan_.set(nSpan->pageId_ + nSpan->n_ - 1, nSpan);

                // 将页号与对应Span的映射存入hash表 IdToSpan_
                // 通过内存对象的地址可以算出它属于哪个页，进而通过IdToSpan找到其所属Span
                // 这样在回收的时候就可以将多个内存对象合并出完整的Span
                for (PAGE_ID i = 0; i < kSpan->n_; i++)
                {
                    // IdToSpan_[kSpan->pageId_ + i] = kSpan;
                    IdToSpan_.set(kSpan->pageId_ + i, kSpan);
                }

                return kSpan;
            }
        }

        // 检查后面的桶有没有Span，如果都没有，就向堆申请128页的span
        // Span *bigSpan = new Span;
        Span *bigSpan = SpanPool_.New();
        void *ptr = SystemAlloc(NPAGES - 1);
        bigSpan->pageId_ = (PAGE_ID)ptr >> PAGE_SHIFT;
        bigSpan->n_ = NPAGES - 1;
        spanlists_[bigSpan->n_].PushFront(bigSpan);

        /* 本来向堆申请128页的span后，我们需要重新执行一遍前面的代码。
        但是我们直接递归调用自己，省去了多余的代码。使代码更加简洁 */
        return NewSpan(k);
    }

    // 获取从内存对象到span的映射
    Span *MapObjectToSpan(void *obj)
    {
        PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT; // 计算页号，知道该段内存属于哪个页

        /* std::unique_lock<std::mutex> lock(page_mtx_); // 加锁
        auto span = IdToSpan_.find(id);
        if (span != IdToSpan_.end())
        {
            return span->second;
        }
        else
        {
            assert(false);
            return nullptr;
        } */

        auto span = (Span *)IdToSpan_.get(id);
        assert(span != nullptr);
        return span;
    }

    // 释放空闲span回到Pagecache，并合并前后相邻的span
    void ReleaseSpanToPageCache(Span *span)
    {
        // span的页的数量超过了128页，说明它不是找Pagecache要的内存，而是直接向堆申请的内存
        // 所以，直接将内存还给系统
        if (span->n_ > NPAGES - 1)
        {
            void *ptr = (void *)(span->pageId_ << PAGE_SHIFT);
            SystemFree(ptr, span->n_ << PAGE_SHIFT);
            //  delete span;
            SpanPool_.Delete(span);

            return;
        }

        // 去合并该Span的前面的Span
        while (true)
        {
            PAGE_ID prevId = span->pageId_ - 1;

            /* auto span = IdToSpan_.find(prevId);

            前面页号没有，就不合并
            if (span == IdToSpan_.end())
                break; */

            auto ret = (Span *)IdToSpan_.get(prevId);
            if (ret == nullptr)
                break;

            // 前面相邻的页正在被使用就不合并了
            Span *prevSpan = ret;
            if (prevSpan->isUse_)
                break;

            // 若合并出超过128页的Span，则无法管理，就不再合并
            if (span->n_ + prevSpan->n_ > NPAGES - 1)
                break;

            span->pageId_ = prevSpan->pageId_;
            span->n_ += prevSpan->n_;

            spanlists_[prevSpan->n_].Erase(prevSpan);
            // delete prevSpan;
            SpanPool_.Delete(prevSpan);
        }

        // 去合并该Span的后面的Span
        while (true)
        {
            PAGE_ID nextId = span->pageId_ + span->n_;

            /* auto span = IdToSpan_.find(nextId);
            // 后面页号没有，就不合并
            if (span == IdToSpan_.end())
                break; */

            auto ret = (Span *)IdToSpan_.get(nextId);
            if(ret == nullptr)
                break;

            // 后面相邻的页正在被使用就不合并了
            Span *nextSpan = ret;
            if (nextSpan->isUse_)
                break;

            // 若合并出超过128页的Span,则无法管理，就不再合并
            if (span->n_ + nextSpan->n_ > NPAGES - 1)
                break;

            span->n_ += nextSpan->n_;

            spanlists_[nextSpan->n_].Erase(nextSpan);
            // delete nextSpan;
            SpanPool_.Delete(nextSpan);
        }

        // 将合并好的Span放入对应的桶里面
        spanlists_[span->n_].PushFront(span);
        span->isUse_ = false;

        // IdToSpan_[span->pageId_] = span;
        // IdToSpan_[span->pageId_ + span->n_ - 1] = span;
        IdToSpan_.set(span->pageId_, span);
        IdToSpan_.set(span->pageId_ + span->n_ - 1, span);
    }

    std::mutex &Get_PageMutex()
    {
        return page_mtx_;
    }

private:
    PageCache() {}
    PageCache(const PageCache &) = delete;

private:
    SpanList spanlists_[NPAGES];
    Memorypool<Span> SpanPool_;

    // std::unordered_map<PAGE_ID, Span *> IdToSpan_; // 页号与对应Span的映射,即该页属于哪个Span
    TCMalloc_PageMap1<32 - PAGE_SHIFT> IdToSpan_; // 使用基数树, 可以不加锁进行查找。避免了锁竞争的消耗

    std::mutex page_mtx_; // 页锁
    static PageCache Pg_Cache;
};

PageCache PageCache::Pg_Cache;